package core;

import java.util.Collection;
import java.util.Iterator;
import java.util.TreeSet;

import core.exception.IllegalAddException;
import core.exception.NotFoundException;
import core.reports.IReportVisitor;
import core.reports.TraversingType;

/**This class represents a storage unit. This class extends 
 * Container, so it can store child ProductGroups, items, and Products.
 * The main additional method of StorageUnit is getContainerForProduct(Product)
 * which returns the ProductGroup, if any, that contains the given product.
 *  
 * @author Natasha
 *
 */

//Container extends ModelObject, so no need to extend ModelObject here
public class StorageUnit extends Container {	

	private static final long serialVersionUID = 3671651439809482552L;
	private Collection<Container> _descendants;

	/**Construct an empty StorageUnit. In principle, a StorageUnit is only useful if 
	 * constructed by the RootStorageUnit through the addContainer(String name) method.
	 * 
	 * @param name The name of the container. Names must be nonempty and unique 
	 * within the parent container.
	 * @throws IllegalArgumentException
	 */
	protected StorageUnit(String name) throws IllegalArgumentException {
		// This method's preconditions are checked in the super class.
		
		super(name);
		_descendants = new TreeSet<Container>();
	}
	
	/**Return a pointer to the container within this StorageUnit that contains
	 * the product. This method works recursively, checking all descendant 
	 * ProductGroups (not just the immediate children). This may return null
	 * if the product is not stored anywhere in the storage unit. It will return
	 * a pointer to this StorageUnit if the product is stored directly in the 
	 * StorageUnit (and not in any ProductGroups).
	 * 
	 * @param product The product to find the Container for.
	 * @return The Container that holds the product, or null if the product
	 * is not in this StorageUnit.
	 */
	public Container getContainerForProduct(Product product){
		// This method has no preconditions
		
		Container ret = null;
		Iterator<Container> iter;
		try{
			iter = ProductManager.instance().getContainersForProduct(product);
		}
		catch(NotFoundException e) {
			iter = null;
		}
		if (iter == null)
			return ret;
		
		// Loop through all the containers the product belongs to, and see if any
		// are the same as the containers that belong to this storage unit.
		while(iter.hasNext()){
			Container prodContainer = iter.next();
			if (_descendants.contains(prodContainer)){
				// if ret is not null, then this product
				// thinks it belongs to two containers in this storage unit
				// which is illegal and means we have data corruption somewhere.
				assert(ret == null);
				ret = prodContainer;
			}
		}
		
		return ret;
	}
	
	/**Return true if the storage unit contains this container
	 * somewhere in it (whether directly or recursively).
	 * 
	 * @param container The possible child
	 * @return True if it is a descendant, false otherwise
	 */
	public boolean hasDescendant(Container container){
		// This method has no preconditions
		return container != null && _descendants.contains(container);
	}
	

	
	/**Add a ProductGroup to this StorageUnit.
	 * 
	 * @param name The name of the new ProductGroup. The name must be nonempty
	 * and unique within this StorageUnit's direct children.
	 * @throws IllegalAddException
	 * @return The new ProductGroup added.
	 */
	@Override
	protected Container addContainer(String name) throws IllegalAddException {
		// Check Preconditions
		if (!canAddContainer(name))
			throw new IllegalAddException("Can't add product group to storage unit");
		
		// Create ProductGroup
		ProductGroup child = new ProductGroup(name);
	
		// update the child's parent pointers
		child._parent = this;
		child._storageUnit = this;		
		
		// Add child to storage unit
		_children.put(name,child);
		_descendants.add(child);
		
		// return a pointer to the child for the user. The user may then want
		// to set the shelf life or other such things.
		return child;
	}
	
	/**This method is for updating the storage unit's _descendants when
	 * a child is removed. 
	 * 
	 * @param toRemove A collection containing all the containers that
	 * no longer exist.
	 */
	protected void updateDescendants(Collection<Container> toRemove){
		// Precondition: this storage unit should only be asked to remove
		// descendants it actually contains. If it doesn't contain something
		// in toRemove, that means there is a bug somewhere.
		assert(_descendants.containsAll(toRemove));
		_descendants.removeAll(toRemove);
	}
	
	/**This method is for internal use by Container and it's subclasses.
	 * It notifies the container that a product group has been added somewhere
	 * in it's subtree.
	 * 
	 * @param descendant The Container added to the subtree.
	 */
	protected void addDescendant(Container descendant){
		// Check Precondition: The descendant should not already be in the
		// storage unit. If we try to add an already existing descendant,
		// there is a bug somewhere.
		assert(!_descendants.contains(descendant));
		
		_descendants.add(descendant);
	}
	
	/**This method is for testing.
	 * 
	 * @return descendants
	 */
	protected Collection<Container> getDescendants(){
		return _descendants;
	}

	@Override
	public void accept(IReportVisitor v) {
		TraversingType t = v.getType();
		
		if (t == TraversingType.PREORDER) v.visitStorageUnit(this);
		
		for(Container c : _children.values()) {
			c.accept(v);
		}
		
		if (t == TraversingType.POSTORDER) v.visitStorageUnit(this);
	}

}
